using System;
using System.Collections.Generic;
using System.Linq;

namespace MyUtils
{
	public class WeakValueCache<TKey, TValue> : SelfMaintainable where TValue : class
	{
		TimeSpan _holdStrongReferenceDuring;

		public WeakValueCache() : this(null, default(TimeSpan)) { }
		public WeakValueCache(TimeSpan holdStrongReferenceDuring) : this(null, holdStrongReferenceDuring) { }
		public WeakValueCache(IEqualityComparer<TKey> comparer) : this(comparer, default(TimeSpan)) {}
		public WeakValueCache(IEqualityComparer<TKey> comparer, TimeSpan holdStrongReferenceDuring)
		{
			_holdStrongReferenceDuring = holdStrongReferenceDuring;
			_dic = new Dictionary<TKey, Entry>(comparer);
		}

		public IEnumerable<KeyValuePair<TKey, TValue>> Entries
		{
			get
			{
				lock (_sync)
				{
					return _dic.Select(x => new KeyValuePair<TKey, TValue>(x.Key, (TValue)x.Value.Object)).Where(x => !Equals(x.Value, default(TValue))).ToArray();
				}
			}
		}

		class Entry
		{
			readonly WeakValueCache<TKey, TValue> _cache;

			public Entry(TValue obj, WeakValueCache<TKey, TValue> cache)
			{
				_cache = cache;
				if (_cache.HoldStrongReferenceDuring > default(TimeSpan))
				{
					_strongReference = obj;
					_lastAccess = DateTime.UtcNow;
				}
				else
				{
					_weakReference = new WeakReference(obj);
				}
			}

			TValue _strongReference;
			WeakReference _weakReference;
			DateTime _lastAccess;

			public object Object
			{
				get
				{
					if (_strongReference != null)
					{
						_lastAccess = DateTime.UtcNow;
						return _strongReference;
					}
					return _weakReference.Target;
				}
			}

			public void Maintain()
			{
				if (_strongReference != null)
				{
					if (DateTime.UtcNow > _lastAccess + _cache.HoldStrongReferenceDuring)
					{
						_weakReference = new WeakReference(_strongReference);
						_strongReference = null;
						_lastAccess = default(DateTime);
					}
				}
			}
		}

		public int Count
		{
			get { return _dic.Count; }
		}

		readonly Dictionary<TKey, Entry> _dic;

		public TimeSpan HoldStrongReferenceDuring
		{
			get { return _holdStrongReferenceDuring; }
			set { _holdStrongReferenceDuring = value; }
		}

		protected override void Maintain()
		{
			PerformScavage();
		}

		public TValue this[TKey key]
		{
			get
			{
				lock (_sync)
				{
					Entry entry;
					if(!_dic.TryGetValue(key, out entry))
					{
						return null;
					}
					var val = (TValue)entry.Object;
					if (ReferenceEquals(val, null))
					{
						_dic.Remove(key);
					}
					return val;
				}
			}
			set
			{
				lock (_sync)
				{
					Scavage();
					_dic[key] = new Entry(value, this);
				}
			}
		}

		/// <summary>
		/// Get or create
		/// </summary>
		public TValue this[TKey key, Func<TValue> factory]
		{
			get
			{
				var cached = this[key];
				if (cached == null)
				{
					this[key] = cached = factory();
				}
				return cached;
			}
		}

		byte _seed;

		void Scavage()
		{
			if (IsScheduled)
			{
				// do not perform extra work, if timer is activated
				return;
			}
			if (unchecked(++_seed) == 0)
			{
				PerformScavage();
			}
		}

		readonly object _sync = new object();

		void PerformScavage()
		{
			lock (_sync)
			{
				foreach (var kvp in _dic)
				{
					kvp.Value.Maintain();
				}

				foreach (var entry in _dic.Where(x => x.Value.Object == null).ToArray())
				{
					_dic.Remove(entry.Key);
				}
			}
		}
	}
}